package com.peak.spring.boot.tools;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.TreeMap;

import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.util.Assert;

import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.net.HttpHeaders;
import com.peak.spring.boot.core.exception.Exceptions;
import com.peak.spring.boot.core.exception.ProjectException;
import com.peak.spring.boot.core.mapper.JsonMapper;

/**
 * Http与Servlet工具类.
 * 
 */
public class Servlets {

	public static final String PAGE_PREFIX = "page_";

	public static final String PARAM_PREFIX = "filter_";

	// -- 常用数值定义 --//
	public static final long ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;

	public static final String TRANS_SUFFIX = "_fmt";

	/**
	 * 设置客户端缓存过期时间 的Header.
	 */
	public static void setExpiresHeader(HttpServletResponse response, long expiresSeconds) {
		// Http 1.0 header, set a fix expires date.
		response.setDateHeader(HttpHeaders.EXPIRES, System.currentTimeMillis() + (expiresSeconds * 1000));
		// Http 1.1 header, set a time after now.
		response.setHeader(HttpHeaders.CACHE_CONTROL, "private, max-age=" + expiresSeconds);
	}

	/**
	 * 设置禁止客户端缓存的Header.
	 */
	public static void setNoCacheHeader(HttpServletResponse response) {
		// Http 1.0 header
		response.setDateHeader(HttpHeaders.EXPIRES, 1L);
		response.addHeader(HttpHeaders.PRAGMA, "no-cache");
		// Http 1.1 header
		response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0");
	}

	/**
	 * 设置LastModified Header.
	 */
	public static void setLastModifiedHeader(HttpServletResponse response, long lastModifiedDate) {
		response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModifiedDate);
	}

	/**
	 * 设置Etag Header.
	 */
	public static void setEtag(HttpServletResponse response, String etag) {
		response.setHeader(HttpHeaders.ETAG, etag);
	}

	/**
	 * 根据浏览器If-Modified-Since Header, 计算文件是否已被修改.
	 * 
	 * 如果无修改, checkIfModify返回false ,设置304 not modify status.
	 * 
	 * @param lastModified 内容的最后修改时间.
	 */
	public static boolean checkIfModifiedSince(HttpServletRequest request, HttpServletResponse response,
			long lastModified) {
		long ifModifiedSince = request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
		if ((ifModifiedSince != -1) && (lastModified < (ifModifiedSince + 1000))) {
			response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			return false;
		}
		return true;
	}

	/**
	 * 根据浏览器 If-None-Match Header, 计算Etag是否已无效.
	 * 
	 * 如果Etag有效, checkIfNoneMatch返回false, 设置304 not modify status.
	 * 
	 * @param etag 内容的ETag.
	 */
	public static boolean checkIfNoneMatchEtag(HttpServletRequest request, HttpServletResponse response, String etag) {
		String headerValue = request.getHeader(HttpHeaders.IF_NONE_MATCH);
		if (headerValue != null) {
			boolean conditionSatisfied = false;
			if (!"*".equals(headerValue)) {
				StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");

				while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
					String currentToken = commaTokenizer.nextToken();
					if (currentToken.trim().equals(etag)) {
						conditionSatisfied = true;
					}
				}
			} else {
				conditionSatisfied = true;
			}

			if (conditionSatisfied) {
				response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
				response.setHeader(HttpHeaders.ETAG, etag);
				return false;
			}
		}
		return true;
	}

	/**
	 * 设置让浏览器弹出下载对话框的Header.
	 * 
	 * @param fileName 下载后的文件名.
	 */
	public static void setFileDownloadHeader(HttpServletRequest request, HttpServletResponse response, String fileName) {
		// 中文文件名支持
		String encodedfileName = null;
		// 替换空格，否则firefox下有空格文件名会被截断,其他浏览器会将空格替换成+号
		encodedfileName = fileName.trim().replaceAll(" ", "_");
		String agent = request.getHeader("User-Agent");
		boolean isMSIE = (agent != null && agent.toUpperCase().indexOf("MSIE") != -1);
		if (isMSIE) {
			encodedfileName = Encodes.urlEncode(fileName);
		} else {
			encodedfileName = new String(fileName.getBytes(), Charsets.UTF_8);
		}

		response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedfileName + "\"");

	}

	/**
	 * 获取分页数据，包括当前页和排序
	 * 
	 * @param request
	 * @return
	 */
	public static PageRequest getPages(ServletRequest request) {
		PageRequest page = null;
		Map<String, Object> params = getParametersStartingWith(request, PAGE_PREFIX);
		int pageNo = Converts.toInt(params.get("pageNo"));
		if (pageNo > 0) {
			pageNo -= 1;
		}
		int pageSize = Converts.toInt(params.get("pageSize"));
		if (pageSize == 0) {
			pageSize = 20;
		}
		String sortStr = Converts.toString(params.get("sort"));
		if (Emptys.isNotEmpty(sortStr)) {
			List<Order> orders = Lists.newArrayList();
			String[] sorts = StringUtils.split(sortStr, ",");
			for (String str : sorts) {
				// 把转换的名称后缀去掉
				str = StringUtils.removeEnd(str, TRANS_SUFFIX);
				String[] orderBy = StringUtils.split(str, "_");
				Order order = new Order(Direction.fromString(orderBy[1]), orderBy[0]);
				orders.add(order);
			}
			Sort sort = new Sort(orders);
			page = new PageRequest(pageNo, pageSize, sort);
		} else {
			page = new PageRequest(pageNo, pageSize);
		}
		return page;
	}
	
	public static PageRequest getPages(Map<String, Object> params) {
		PageRequest page = null;
		int pageNo = Converts.toInt(params.get("pageNo"));
		if (pageNo > 0) {
			pageNo -= 1;
		}
		int pageSize = Converts.toInt(params.get("pageSize"));
		if (pageSize == 0) {
			pageSize = 20;
		}
		String sortStr = Converts.toString(params.get("sort"));
		if (Emptys.isNotEmpty(sortStr)) {
			List<Order> orders = Lists.newArrayList();
			String[] sorts = StringUtils.split(sortStr, ",");
			for (String str : sorts) {
				// 把转换的名称后缀去掉
				str = StringUtils.removeEnd(str, TRANS_SUFFIX);
				String[] orderBy = StringUtils.split(str, "_");
				Order order = new Order(Direction.fromString(orderBy[1]), orderBy[0]);
				orders.add(order);
			}
			Sort sort = new Sort(orders);
			page = new PageRequest(pageNo, pageSize, sort);
		} else {
			page = new PageRequest(pageNo, pageSize);
		}
		return page;
	}

	/**
	 * 把request中的参数绑定到对象上，要求fieldName相同。<br/>
	 * 新建对象时使用<br/>
	 * 不支持级联的对象，需要自行实现
	 * 
	 * @param request
	 * @param clazz
	 * @return
	 */
	public static <T> T bind(ServletRequest request, Class<T> clazz) {
		try {
			return bind(request, clazz.newInstance());
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 把request中的参数绑定到对象上，要求fieldName相同。<br/>
	 * 更新对象时使用<br/>
	 * 不支持级联的对象，需要自行实现
	 * 
	 * @param request
	 * @param javaBean
	 * @return
	 */
	public static <T> T bind(ServletRequest request, T javaBean) {
		Map<String, Object> params = getParametersStartingWith(request, "");
		return Converts.fromMap(params, javaBean);
	}

	/**
	 * 把request中的参数绑定到对象上，要求fieldName相同。<br/>
	 * 新建对象时使用<br/>
	 * 不支持级联的对象，需要自行实现
	 * 
	 * @param request
	 * @param clazz
	 * @param prefix
	 * @return
	 */
	public static <T> T bind(ServletRequest request, Class<T> clazz, String prefix) {
		try {
			return bind(request, clazz.newInstance(), prefix);
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 把request中的参数绑定到对象上，要求fieldName相同。<br/>
	 * 更新对象时使用<br/>
	 * 不支持级联的对象，需要自行实现
	 * 
	 * @param request
	 * @param javaBean
	 * @param prefix
	 * @return
	 */
	public static <T> T bind(ServletRequest request, T javaBean, String prefix) {
		Map<String, Object> params = getParametersStartingWith(request, prefix);
		return Converts.fromMap(params, javaBean);
	}

	/**
	 * 取得带相同前缀的Request Parameters, copy from spring WebUtils. 返回的结果的Parameter名已去除前缀. 默认前缀为fliter_
	 * 
	 * @param request
	 * @return
	 */
	public static Map<String, Object> getParameters(ServletRequest request) {
		return getParametersStartingWith(request, PARAM_PREFIX);
	}

	/**
	 * 取得带相同前缀的Request Parameters, copy from spring WebUtils.<br/>
	 * 增加如果是单一参数且等于''则认为是null
	 * 
	 * @param request
	 * @param prefix
	 * @return
	 */
	public static Map<String, Object> getParametersStartingWith(ServletRequest request, String prefix) {
		Assert.notNull(request, "Request must not be null");
		Enumeration<String> paramNames = request.getParameterNames();
		Map<String, Object> params = new TreeMap<String, Object>();
		if (prefix == null) {
			prefix = "";
		}
		while (paramNames != null && paramNames.hasMoreElements()) {
			String paramName = paramNames.nextElement();
			if ("".equals(prefix) || paramName.startsWith(prefix)) {
				String unprefixed = paramName.substring(prefix.length());
				String[] values = request.getParameterValues(paramName);
				if (values == null || values.length == 0) {
					// Do nothing, no values found at all.
				} else if (values.length > 1) {
					params.put(unprefixed, values);
				} else {
					String value = values[0];
					if (Emptys.isEmpty(value)) {
						value = null;
					}
					params.put(unprefixed, StringUtils.trim(value));
				}
			}
		}
		// 把payload里面的参数也取出来
		Map<String, Object> map = getParamFromPayload(request);
		if (Emptys.isNotEmpty(map)) {
			for (Entry<String, Object> entry : map.entrySet()) {
				String paramName = entry.getKey();
				if ("".equals(prefix) || paramName.startsWith(prefix)) {
					String unprefixed = paramName.substring(prefix.length());
					Object value = entry.getValue();
					if (value instanceof String) {
						value = StringUtils.trim(Converts.toString(value));
					}
					params.put(unprefixed, value);
				}
			}
		}
		return params;
	}
	
	@SuppressWarnings("unchecked")
	public static Map<String, Object> getParamFromPayload(ServletRequest request) {
		try {
			ServletInputStream inputStream = request.getInputStream();
			String json = IOUtils.toString(inputStream, Encodes.DEFAULT_URL_ENCODING2);
			if (Emptys.isNotEmpty(json)) {
				return JsonMapper.nonDefaultMapper().fromJson(json, Map.class);
			}
		} catch (IOException e) {
			throw Exceptions.unchecked(e);
		}
		return null;
	}
	
	/**
	 * 取的Request Headers<br/>
	 * 
	 * @param request
	 * @return
	 */
	public static Map<String, String> getHeaders(HttpServletRequest request) {
		Assert.notNull(request, "Request must not be null");
		Enumeration<String> headerNames = request.getHeaderNames();
		Map<String, String> headers = new TreeMap<String, String>();
		while (headerNames != null && headerNames.hasMoreElements()) {
			String headerName = headerNames.nextElement();
			Enumeration<String> values = request.getHeaders(headerName);
			StringBuilder sb = new StringBuilder();
			while (values != null && values.hasMoreElements()) {
				sb.append(values.nextElement()).append(",");
			}
			sb.deleteCharAt(sb.length() - 1);
			headers.put(headerName, sb.toString());
		}
		return headers;
	}

	/**
	 * 客户端对Http Basic验证的 Header进行编码.
	 */
	public static String encodeHttpBasic(String userName, String password) {
		String encode = userName + ":" + password;
		return "Basic " + Encodes.encodeBase64(encode.getBytes());
	}

	/**
	 * 获取客户端机器的IP
	 * 
	 * @param request
	 * @return
	 */
	public static String getIp(HttpServletRequest request) {
		String ipAddress = null;
		// ipAddress = request.getRemoteAddr();
		ipAddress = request.getHeader("x-forwarded-for");
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("Proxy-Client-IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
			ipAddress = request.getRemoteAddr();
			if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
				// 根据网卡取本机配置的IP
				InetAddress inet = null;
				try {
					inet = InetAddress.getLocalHost();
					ipAddress = inet.getHostAddress();
				} catch (UnknownHostException e) {

				}
			}
		}

		// 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
		if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
															// = 15
			if (ipAddress.indexOf(",") > 0) {
				ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
			}
		}
		return ipAddress;
	}

	/**
	 * 获取contextPath
	 * 
	 * @param request
	 * @return
	 */
	public static String getContextPath(ServletRequest request) {
		return request.getServletContext().getContextPath();
	}

	/**
	 * 获取realPath
	 * 
	 * @param request
	 * @return
	 */
	public static String getRealPath(ServletRequest request) {
		return request.getServletContext().getRealPath("/");
	}

	public static String getCookie(HttpServletRequest request, String key) {
		Cookie[] cookies = request.getCookies();
		String value = null;
		if (Emptys.isNotEmpty(cookies)) {
			value = Arrays.asList(cookies).stream().filter(p -> key.equals(p.getName())).map(p -> p.getValue())
					.findFirst().orElse(null);
		}
		return value;
	}

	public static Cookie getCookie2(HttpServletRequest request, String key) {
		Cookie[] cookies = request.getCookies();
		Cookie value = null;
		if (Emptys.isNotEmpty(cookies)) {
			value = Arrays.asList(cookies).stream().filter(p -> key.equals(p.getName())).findFirst().orElse(null);
		}
		return value;
	}
	
	public static boolean isAjax(HttpServletRequest request) {
		return "xmlhttprequest".equalsIgnoreCase(request.getHeader("x-requested-with"));
	}

	public static void sendJson(HttpServletResponse response, Object content) {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json; charset=utf-8");
		PrintWriter out = null;
		String json = null;
		try {
			out = response.getWriter();
			json = JsonMapper.nonDefaultMapper().toJson(content);
			out.write(json);
		} catch (IOException e) {
			throw new ProjectException(json, e);
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}
}
